package com.tvunetworks.center.device.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.util.ObjectUtils;

import java.util.Collections;
import java.util.List;

/**
 * @author Etan Yang
 * @data: 2021/7/6 17:47
 * @program: tvucc-playout
 * @description:
 */
@Slf4j
public class RedisLock {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    private final long lockTimeoutMills;
    private final long reAcquireLockInterval;

    private static final String SUCCESS_FLAG = "OK";

    private static final RedisScript<String> SET_IF_ABSENT_OR_EQUAL_SCRIPT = new DefaultRedisScript<>(
            "if redis.call('GET',KEYS[1]) == ARGV[1] or redis.call('SET',KEYS[1],ARGV[1],'PX',ARGV[2],'NX') then "
                    + "return 'OK' "
                    + "end "
                    + "return 'FALSE'",
            String.class
    );


    private static final RedisScript<String> COMPARE_AND_DELETE_SCRIPT = new DefaultRedisScript<>(
            "if redis.call('GET',KEYS[1]) == ARGV[1] then "
                    + "redis.call('DEL',KEYS[1]) "
                    + "return 'OK' "
                    + "end "
                    + "return 'FALSE'",
            String.class
    );

    private static final RedisScript<String> MULTI_COMPARE_AND_DELETE_SCRIPT = new DefaultRedisScript<>(
            "local n = table.getn(KEYS) "
                    + "for i=1,n do "
                    + " if redis.call('GET',KEYS[i]) == ARGV[1] then "
                    + "   redis.call('DEL',KEYS[i]) "
                    + " end "
                    + "end "
                    + "return 'OK' ",
            String.class
    );

    public RedisLock(long lockTimeoutMills, long reAcquireLockInterval) {
        this.lockTimeoutMills = lockTimeoutMills;
        this.reAcquireLockInterval = reAcquireLockInterval;
    }

    /**
     *
     * @param key 参数
     * @return tryLock
     */
    public boolean tryLock(String key) {
        return internalTryLock(key, getLockValue());
    }

    private boolean internalTryLock(String key, String value) {
        return setIfAbsentOrEqual(key, value, lockTimeoutMills);
    }

    private String getLockValue() {
        return String.valueOf(Thread.currentThread().getId());
    }

    /**
     * 锁
     * @param key 参数
     * @return boolean
     */
    public boolean lock(String key) {
        while (!internalTryLock(key, getLockValue())) {
            try {
                Thread.sleep(reAcquireLockInterval);
            } catch (InterruptedException e) {
                log.error("RedisLock111>>>>e:[{}]", e);
                throw new RuntimeException(e);
            }
        }
        return true;
    }

    /**
     *
     * @param key 参数
     * @return boolean
     */
    public boolean lockOutTime(String key) {
        long outTime = 0;
        while (!internalTryLock(key, getLockValue())) {
            try {
                if (outTime > 60000) {
                    return false;
                }
                Thread.sleep(reAcquireLockInterval);
                outTime += reAcquireLockInterval;
            } catch (InterruptedException e) {
                log.error("RedisLockOutTime>>>>>e:[{}]", e);
                throw new RuntimeException(e);
            }
        }
        return true;
    }

    /**
     * 获取锁的等待时间
     *
     * @param key 参数
     * @param timeoutMs 参数
     * @return boolean
     * @throws InterruptedException 编译异常
     */
    public boolean lock(String key, long timeoutMs) {
        long start = System.currentTimeMillis();
        while (!internalTryLock(key, getLockValue())) {
            long leftWaitTime = timeoutMs - (System.currentTimeMillis() - start);
            if (leftWaitTime < 0) {
                throw new RuntimeException("获取分布式锁超时");
            }
            try {
                Thread.sleep(Math.min(reAcquireLockInterval, leftWaitTime));
            } catch (InterruptedException e) {
                log.error("redisLock>>>>>exception:[{}]", e);
                throw new RuntimeException(e);
            }
        }
        return true;
    }

    /**
     * 如果当前线程已经不是锁的持有者，返回false
     *
     * @param key 参数
     * @return boolean
     */
    public boolean unlock(String key) {
        return compareAndDelete(toList(key), getLockValue());
    }

    /**
     *
     * @param key 参数
     * @param value 参数
     * @param timeoutMills 参数
     * @return boolean
     */
    public boolean setIfAbsentOrEqual(String key, String value, long timeoutMills) {
        String result = stringRedisTemplate.execute(
                SET_IF_ABSENT_OR_EQUAL_SCRIPT,
                toList(key),
                value,
                String.valueOf(timeoutMills)
        );
        return isSuccess(result);

    }

    private List<String> toList(String key) {
        return Collections.singletonList(key);
    }

    /**
     *
     * @param keys 参数
     * @param expectValue 参数
     * @return boolean
     */
    public boolean compareAndDelete(final List<String> keys, final String expectValue) {
        assert !ObjectUtils.isEmpty(keys);
        String result;
        if (keys.size() == 1) {
            result = stringRedisTemplate.execute(
                    COMPARE_AND_DELETE_SCRIPT,
                    keys,
                    expectValue
            );
        } else {
            result = stringRedisTemplate.execute(
                    MULTI_COMPARE_AND_DELETE_SCRIPT,
                    keys,
                    expectValue
            );
        }
        return isSuccess(result);
    }

    private boolean isSuccess(String result) {
        return SUCCESS_FLAG.equals(result);
    }


}
